/* eslint-disable func-names */
import { SERVICES_KEY, UPDATE_REDUCER, UPDATE_LOADING_REDUCER, NAMESPACE_SEP } from './constants';
import { isFunction, isActionType } from './utils';

const dealActionType = (type, namespace) =>
  isActionType(type) ? type : `${namespace}${NAMESPACE_SEP}${type}`;

const _typeof = payload => {
  const type = typeof payload;
  if (type !== 'object') return type;
  if (Array.isArray(payload) && payload.length) return 'array';
  if (isFunction(payload)) return 'function';

  return 'unknown';
};
/**
 * @description enhance update effect
 * @param {*} param
 */
const createUpdateEffect = ({ put }, namespace) =>
  function*(payload) {
    yield put({
      type: `${namespace}${NAMESPACE_SEP}${UPDATE_REDUCER}`,
      payload,
    });
  };

/**
 * @description enhance select effect
 * @param {*} param
 * @param {*} namespace  当前命名空间
 */
const createSelectEffect = ({ select }, namespace) =>
  function*(payload) {
    let selectResult;
    const payloadType = _typeof(payload);
    const state = yield select();

    switch (payloadType) {
      case 'undefined':
        selectResult = state[namespace];
        break;
      case 'string':
        selectResult = state[payload];
        break;
      case 'array':
        selectResult = payload.reduce((pre, key) => {
          pre[key] = state[key];
          return pre;
        }, {});
        break;
      case 'function':
        selectResult = payload(state);
        break;
      default:
        selectResult = null;
        break;
    }

    return selectResult;
  };

/**
 * @description put effect
 * @param {*} param0
 */
const createPutEffect = ({ put }, namespace) => {
  function* putEffect({ type, payload }) {
    return yield put.resolve({ type: dealActionType(type, namespace), payload });
  }

  function* putSyncEffect({ type, payload }) {
    return yield put({ type: dealActionType(type, namespace), payload });
  }

  putEffect.sync = putSyncEffect;

  return putEffect;
};

const createCallEffect = ({ call, put }, namespace, services) =>
  function*(...agrs) {
    if (agrs.length) {
      let result;
      let serviceName = agrs[0].name;
      if (services) {
        const serviceKeys = Object.keys(services);
        serviceKeys.forEach(key => {
          if (services[key] === agrs[0] && serviceName !== key) {
            serviceName = key;
          }
        });
      }
      const actionType = `${namespace}${NAMESPACE_SEP}${UPDATE_LOADING_REDUCER}`;
      try {
        yield put({
          type: actionType,
          payload: { [serviceName]: true },
        });
        result = yield call(...agrs);
      } catch(e) {
        throw e;
      } finally {
        yield put({
          type: actionType,
          payload: { [serviceName]: false },
        });
      }
      return result;
    }
    throw new Error('call effect 参数不正确');
  };

const onEffectPlugin = () => ({
  onEffect(effect, sagaEffects, model) {
    const { namespace } = model;
    // feat: 用于使用@service时注入services方法
    const services = model[SERVICES_KEY];

    return function*(action) {
      return yield effect(
        { ...action, ...services },
        {
          ...sagaEffects,
          // update effect
          update: createUpdateEffect(sagaEffects, namespace),
          // select effect
          select: createSelectEffect(sagaEffects, namespace),
          // put effect
          put: createPutEffect(sagaEffects, namespace),
          // call effect
          call: createCallEffect(sagaEffects, namespace, services),
        },
      );
    };
  },
});

export default onEffectPlugin;
